/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.sonar.util.collections.obs;

import bgu.sonar.util.evt.EventListeners;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 *
 * @author bennyl
 */
public class ObservableList<T> implements List<T> {

    private List<T> wrapee;
    private EventListeners<ListListener> listeners = EventListeners.create(ListListener.class);

    public ObservableList(List<T> wrapee) {
        this.wrapee = wrapee;
    }

    public ObservableList() {
        wrapee = new LinkedList<>();
    }

    public EventListeners<ListListener> getListeners() {
        return listeners;
    }

    @Override
    public int size() {
        return wrapee.size();
    }

    @Override
    public boolean isEmpty() {
        return wrapee.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return wrapee.contains(o);
    }

    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            Iterator<T> it = wrapee.iterator();
            T current = null;

            @Override
            public boolean hasNext() {
                return it.hasNext();
            }

            @Override
            public T next() {
                current = it.next();
                return current;
            }

            @Override
            public void remove() {
                it.remove();
                listeners.fire().onItemsRemoved(ObservableList.this, Collections.singleton(current));
            }
        };
    }

    @Override
    public Object[] toArray() {
        return wrapee.toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return wrapee.toArray(a);
    }

    @Override
    public boolean add(T e) {
        if (wrapee.add(e)) {
            listeners.fire().onItemsAdded(this, Collections.singleton(e), wrapee.size() - 1);
            return true;
        }

        return false;
    }

    @Override
    public boolean remove(Object o) {
        if (wrapee.remove(o)) {
            listeners.fire().onItemsRemoved(this, Collections.singleton(o));
            return true;
        }

        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return wrapee.containsAll(c);
    }

    @Override
    public boolean addAll(Collection<? extends T> c) {
        if (wrapee.addAll(c)) {
            listeners.fire().onItemsAdded(this, c, wrapee.size() - c.size());
            return true;
        }

        return false;
    }

    public void addAll(T... c) {
        addAll(Arrays.asList(c));
    }

    @Override
    public boolean addAll(int index, Collection<? extends T> c) {
        if (wrapee.addAll(index, c)) {
            listeners.fire().onItemsAdded(this, c, index);
            return true;
        }

        return false;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        if (wrapee.remove(c)) {
            listeners.fire().onItemsRemoved(this, c);
            return true;
        }

        return false;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        if (wrapee.retainAll(c)) {
            listeners.fire().onListChanged(this);
            return true;
        }

        return false;
    }

    @Override
    public void clear() {
        if (!wrapee.isEmpty()) {
            wrapee.clear();
            listeners.fire().onListChanged(this);
        }
    }

    @Override
    public boolean equals(Object o) {
        return wrapee.equals(o);
    }

    @Override
    public int hashCode() {
        return wrapee.hashCode();
    }

    @Override
    public T get(int index) {
        return wrapee.get(index);
    }

    @Override
    public T set(int index, T element) {
        T old = wrapee.set(index, element);
        listeners.fire().onItemsRemoved(this, Collections.singleton(old));
        listeners.fire().onItemsAdded(this, Collections.singleton(element), index);
        return old;
    }

    @Override
    public void add(int index, T element) {
        wrapee.add(index, element);
        listeners.fire().onItemsAdded(this, Collections.singleton(element), index);
    }

    @Override
    public T remove(int index) {
        T removede = wrapee.remove(index);
        listeners.fire().onItemsRemoved(this, Collections.singleton(removede));
        return removede;
    }

    @Override
    public int indexOf(Object o) {
        return wrapee.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return wrapee.lastIndexOf(o);
    }

    @Override
    public ListIterator<T> listIterator() {
        return wrapIterator(wrapee.listIterator());
    }

    @Override
    public ListIterator<T> listIterator(int index) {
        return wrapIterator(wrapee.listIterator(index));
    }

    @Override
    public List<T> subList(int fromIndex, int toIndex) {
        return wrapee.subList(fromIndex, toIndex);
    }

    private ListIterator<T> wrapIterator(final ListIterator<T> i) {
        return new ListIterator<T>() {
            @Override
            public boolean hasNext() {
                return i.hasNext();
            }

            @Override
            public T next() {
                return i.next();
            }

            @Override
            public boolean hasPrevious() {
                return i.hasPrevious();
            }

            @Override
            public T previous() {
                return i.previous();
            }

            @Override
            public int nextIndex() {
                return i.nextIndex();
            }

            @Override
            public int previousIndex() {
                return i.previousIndex();
            }

            @Override
            public void remove() {
                i.previous();
                T cur = i.next();
                i.remove();
                listeners.fire().onItemsRemoved(ObservableList.this, Collections.singleton(cur));
            }

            @Override
            public void set(T e) {
                i.previous();
                int idx = i.nextIndex();
                T cur = i.next();

                i.set(e);
                listeners.fire().onItemsRemoved(ObservableList.this, Collections.singleton(cur));
                listeners.fire().onItemsAdded(ObservableList.this, Collections.singleton(e), idx);
            }

            @Override
            public void add(T e) {
                i.add(e);
                listeners.fire().onItemsAdded(ObservableList.this, Collections.singleton(e), i.nextIndex());
            }
        };
    }

    public interface ListListener<T> {

        void onItemsAdded(ObservableList<T> list, Collection<T> items, int pos);

        void onItemsRemoved(ObservableList<T> list, Collection<T> items);

        /**
         * will be called when operations like retainAll called - the results of
         * those operation atomically include removing and adding - therfore it
         * cannot be expressed as remove/insert operations
         *
         * @param list
         */
        void onListChanged(ObservableList<T> list);
    }
}
